package owg.util.opengl;

import java.awt.Point;
import java.nio.ByteBuffer;

import com.jogamp.opengl.GL;

import com.jogamp.common.nio.Buffers;
import com.jogamp.opengl.util.GLBuffers;

import owg.util.euclidian.Compass;

/**Container object class for texture arrays with an assigned origin adjustment*/
public class Tex
{
	/**Constant for setRepeating indicating to clamp the texture coordinates.*/
	public static final int NO_REPEAT = 0;
	/**Constant for setRepeating indicating to clamp the vertical texture coordinate and 
	 * repeat the texture in the x-direction.*/
	public static final int REPEAT_H = 1;
	/**Constant for setRepeating indicating to clamp the horizontal texture coordinate and 
	 * repeat the texture in the y-direction.*/
	public static final int REPEAT_V = 2;
	/**Constant for setRepeating indicating to repeat the texture in both directions.*/
	public static final int REPEAT_BOTH = 3;
	
	/**Whether textures have been created on the GL*/
	private boolean isUploaded;
	/**ARGB Byte data from the texture file, by frame*/
	public final byte[][] data;
	/**An array of OpenGL texture pointers, referring to the subimages(frames) of this object.
	 * The textures must have similar sizes.*/
	private final int[] tex;
	/**Whether to generate mipmaps*/
	public final boolean mipmap;
	/**Whether to use linear magnification filtering*/
	public final boolean bilinearMag;
	/**The origin point on the bitmap image. 
	 * When using drawTexture in GFX with orientation NORTHWEST, 
	 * this is the point on the image that is placed on the x/y/z coordinate that is sent to the method.
	 * When using other orientations, their relative offsets are added to this origin.*/
	public final Point origin;
	/**The number of subimages in the texture array.*/
	public final short length;
	/**The file name, without the extension.*/
	public final String name;
	/**The width of the image. Note that the actual textures may have a larger size if forcePOTS is enabled.
	 * The texture width will then be the next power of two from this width, or width/xMult.*/
	public final int width;
	/**The height of the image. Note that the actual textures may have a larger size if forcePOTS is enabled.
	 * The texture height will then be the next power of two from this height, or height/yMult.*/
	public final int height;
	/**Multipliers for texture coordinates used when drawing this image. 
	 * Will always be 1 if the dimensions are powers of 2, may otherwise be less than 1.*/
	public final float xMult, yMult;
	
	public Tex(String name, byte[][] data, int width, int height, float xMult, float yMult, boolean mipmap, boolean magFilter)
	{
		this(name, data, new Point(0,0), width, height, xMult, yMult, mipmap, magFilter);
	}
	public Tex(String name, byte[][] data, Point origin, int width, int height, float xMult, float yMult, boolean mipmap, boolean magFilter)
	{
		this.tex = new int[data.length];
		this.xMult = xMult;
		this.yMult = yMult;
		this.data=data;
		this.origin=origin;
		this.name=name;
		this.width = width;
		this.height = height;
		this.mipmap = mipmap;
		this.bilinearMag = magFilter;
		length = (short)data.length;
		isUploaded = false;
		
		assert noNulls(data) : "Nulls in texture array not permitted: "+name;
	}
	
	private static boolean noNulls(Object[] data)
	{
		if(data == null)
		{
			System.err.println("Warning: Array is null.");
			return false;
		}
		
		for(int i = 0; i<data.length; i++)
		{
			if(data[i] == null)
			{
				System.err.println("Warning: Null at position: "+i);
				return false;
			}
		}
		return true;
	}
	
	public int tex(EGL<?> egl, int index)
	{
		if(!isUploaded)
		{
			GL gl = egl.getGL();
			gl.glGenTextures(length, tex, 0);
			for(int i = 0; i<length; i++)
			{
				ByteBuffer buf = ByteBuffer.wrap(data[i]);
				gl.glBindTexture(GL.GL_TEXTURE_2D, tex[i]);
				if(mipmap) 
				{
					//Note: gluBuild2DMipmaps upscales NPOTs. No need to do anything about that...
					if(gl.isGL2())
						egl.getGLU().gluBuild2DMipmaps(GL.GL_TEXTURE_2D, GL.GL_RGBA8, width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, buf);
					else
					{
						gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA8, width, height, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, buf);
						gl.glGenerateMipmap(GL.GL_TEXTURE_2D);
					}
				}
				else if(xMult == 1.0f && yMult == 1.0f)
					gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA8, width, height, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, buf);
				else
				{
					int texWidth = GLBuffers.getNextPowerOf2(width);
					int texHeight = GLBuffers.getNextPowerOf2(height);
					//Allocate empty data
					gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA8, texWidth, texHeight, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, Buffers.newDirectByteBuffer(texWidth*texHeight*4));
					//Transfer to relevant part of texture
					gl.glTexSubImage2D(GL.GL_TEXTURE_2D, 0, 0, 0, width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, buf);
				}
				
				if(mipmap)
					gl.glTexParameteri(GL.GL_TEXTURE_2D,GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST_MIPMAP_LINEAR);
				else
					gl.glTexParameteri(GL.GL_TEXTURE_2D,GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
				
				gl.glTexParameteri(GL.GL_TEXTURE_2D,GL.GL_TEXTURE_MAG_FILTER, bilinearMag?GL.GL_LINEAR:GL.GL_NEAREST);
				
				gl.glTexParameteri(GL.GL_TEXTURE_2D,GL.GL_TEXTURE_WRAP_S, GL.GL_REPEAT);
				gl.glTexParameteri(GL.GL_TEXTURE_2D,GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT);
			}
			isUploaded = true;
		}
		return tex[index];
	}
	public int getWidth()
	{
		return width;
	}
	public int getHeight()
	{
		return height;
	}
	public Point getOrigin(Compass orientation)
	{
		int x=(int) (getWidth()*orientation.dx), y=(int)(getHeight()*orientation.dy);
		return new Point(x+origin.x,y+origin.y);
	}
	@Override
	public String toString()
	{
		return name;
	}
	
	/**Deallocate all system resources occupied by this texture.*/
	public void destroy(GL gl)
	{
		TextureLib.removeTexture(this);
		gl.glDeleteTextures(tex.length, tex, 0);
	}
	
	@Override
	public boolean equals(Object other)
	{
		return this == other;
	}
	
	@Override
	public int hashCode()
	{
		return name.hashCode();
	}
}
